package com.zl.quickdoc;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.core.type.TypeReference;
import com.zl.quickdoc.constant.QuickDocC;
import com.zl.quickdoc.constant.QuickDocEnum;
import com.zl.quickdoc.format.IApiFormat;
import com.zl.quickdoc.framework.IApiFramework;
import com.zl.quickdoc.model.ApiAction;
import com.zl.quickdoc.model.ApiDocument;
import com.zl.quickdoc.model.ApiField;
import com.zl.quickdoc.model.ApiModule;
import com.zl.quickdoc.model.DocCfgParam;
import com.zl.quickdoc.model.DocChangeActionDesc;
import com.zl.quickdoc.model.DocChangeApis;
import com.zl.quickdoc.model.DocChangeFieldDesc;
import com.zl.quickdoc.model.DocChangeModuleDesc;
import com.zl.quickdoc.resolver.JavaUnitParserFactory;
import com.zl.quickdoc.utils.JsonUtils;

/**
 * 文档解析入口
 * @author zhouliang
 *
 */
public class SpringApiFactory {

    /** 接口框架 */
    private IApiFramework framework;
    
    /** 初始化属性 */
    private DocCfgParam cfg;
    
    /** 接口属性 */
    private ApiDocument apiDoc;
    
    public IApiFramework getFramework() {
        return framework;
    }
    public void setFramework(IApiFramework framework) {
        this.framework = framework;
    }
    public ApiDocument getApiDoc() {
        return apiDoc;
    }
    public void setApiDoc(ApiDocument apiDoc) {
        this.apiDoc = apiDoc;
    }
    
    public SpringApiFactory(IApiFramework framework, DocCfgParam cfg) {
        this.framework = framework;
        this.cfg = cfg;
    }
    
    /**
     * 解析源码
     */
    public void resolve() throws Exception {
        List<ApiModule> apiModules = JavaUnitParserFactory.resolve(this.cfg.getJavaPath(),this.cfg.getClassPath(), this.framework);
        Map<String, Object> properties = this.cfg.getProperties();
        Map<String, Object> map = JsonUtils.getObjectMapper().convertValue(this.cfg, new TypeReference<Map<String, Object>>() {});
        properties.putAll(map);
        this.apiDoc = new ApiDocument(apiModules,this.cfg.getProperties());
    }

    /**
     * 生成接口文档
     */
    public void build(IApiFormat format) {
       
        try {
            if(this.apiDoc == null) {
                this.resolve();
            }
            System.out.println(">>>>>>>>>>构建文档>>类型="+format.formatType());
            this.apiDoc.setChangedata(versionHandler());
            format.format(this.apiDoc, this.cfg);
            autoOpenDoc();
        } catch (Exception e) {
            System.out.println("处理失败");
            e.printStackTrace();
        }
        System.out.println(">>>>>>>>>>>>>>完成>>类型="+format.formatType());
    }
    
    private void autoOpenDoc() throws IOException{
        if(!this.cfg.getAutoOpenFlag() || StringUtils.isBlank(this.apiDoc.getOutputPath())){
            return;
        }
        
        String os = System.getProperty("os.name").toLowerCase();
        String command;
        if (os.contains("win")) {
            // Windows系统
            command = "cmd /c start \"\" \"" + this.apiDoc.getOutputPath() + "\"";
        } else if (os.contains("mac") || os.contains("nix") || os.contains("nux") || os.contains("aix")) {
            // Mac或Linux系统
            command = "open " + this.apiDoc.getOutputPath();
        } else {
            // 未知操作系统
            System.out.println(">>>>不支持的操作系统["+os+"], 禁用自动打开文档");
            return;
        }
        try {
            System.out.println(">>>>自动打开文档: " + this.apiDoc.getOutputPath());
            Runtime.getRuntime().exec(command);
        } catch (IOException e) {
            System.out.println(">>>>自动打开文档异常: " + e.getMessage());
        }
    }
    
    private List<DocChangeApis> versionHandler() throws IOException{
        List<DocChangeApis> changedata = new ArrayList<>();
        if(!this.cfg.getVersionFlag()){
            return changedata;
        }
        File versionFile = new File(this.cfg.getApiPath()+File.separator+QuickDocC.CHILDREN_PATH_VERSION+File.separator+this.cfg.getProjectTitle());
        if(!versionFile.exists() || !versionFile.isDirectory()){
            versionFile.mkdirs();
        }
        String nowfilename = String.format(QuickDocC.NAME_VERSION_APIS,this.cfg.getVersion());
        String rootpath = versionFile.getPath();
        File[] list = versionFile.listFiles();
        String oldapitext = null;
        String oldchangetext = null;
        if(list!=null && list.length>0) {
            File apifile = null;
            File changefile = null;
            for(File x:list) {
                if(!x.isFile()) {
                    continue;
                }
                if(x.getName().matches("^"+ QuickDocC.NAME_VERSION_APIS.replace("%s", ".*")+"$")
                        && !nowfilename.equals(x.getName())) {
                    if(apifile==null) {
                        apifile = x;
                    }else if(x.lastModified() > apifile.lastModified()){
                        apifile = x;
                    }
                }else if(x.getName().equals(QuickDocC.NAME_VERSION_CHANGES)) {
                    if(changefile==null) {
                        changefile = x;
                    }else if(x.lastModified() > changefile.lastModified()){
                        changefile = x;
                    }
                }
            }
            if(apifile != null) {
                oldapitext = FileUtils.readFileToString(apifile, StandardCharsets.UTF_8.name());
            }
            if(changefile != null) {
                oldchangetext = FileUtils.readFileToString(changefile, StandardCharsets.UTF_8.name());
            }
        }
        
        //生成一个历史版本
        versionFile = new File(rootpath + File.separator + nowfilename);
        FileUtils.writeStringToFile(versionFile, JsonUtils.toFormatJson(this.apiDoc), StandardCharsets.UTF_8.name());
        
        boolean firstFlag = versionHandler(this.apiDoc, oldapitext, oldchangetext, changedata);
        if(firstFlag) {
            DocChangeApis changeApiVO = new DocChangeApis();
            changeApiVO.setVersion(this.cfg.getVersion());
            changeApiVO.setRemark(Arrays.asList(QuickDocC.DESC_VERSION_INIT));
            changedata.add(changeApiVO);
        }
        versionFile = new File(rootpath + File.separator + QuickDocC.NAME_VERSION_CHANGES);
        FileUtils.writeStringToFile(versionFile, JsonUtils.toFormatJson(changedata), StandardCharsets.UTF_8.name());
        return changedata;
    }
    private boolean versionHandler(ApiDocument doc,String oldapitext, String oldchangetext, List<DocChangeApis> changedata) {
        if(StringUtils.isBlank(oldapitext) || StringUtils.isBlank(oldchangetext) ){
            return true;
        }
        List<DocChangeApis> changedata2 = JsonUtils.toBeans(oldchangetext, DocChangeApis.class);
        if(changedata2.isEmpty()) {
            return true;
        }
        for(DocChangeApis x:changedata2) {
            if(!this.cfg.getVersion().equals(x.getVersion())) {
                changedata.add(x);
            }
        }
        if(changedata.isEmpty()) {
            return true;
        }
        
        DocChangeApis changeApiVO = new DocChangeApis();
        changedata.add(changeApiVO);
        changeApiVO.setVersion(this.cfg.getVersion());
        List<DocChangeModuleDesc> changes = new ArrayList<>();
        changeApiVO.setChanges(changes);
        List<String> remark = new ArrayList<>();
        if(!this.cfg.getChangeHis().isEmpty() && StringUtils.isNotBlank(this.cfg.getChangeHis().get(this.cfg.getVersion()))) {
          String histext = this.cfg.getChangeHis().get(this.cfg.getVersion());
          remark = Arrays.asList(histext.split("\n"));
        }
        changeApiVO.setRemark(remark);
        
        ApiDocument olddoc = JsonUtils.toBean(oldapitext,ApiDocument.class);
        List<ApiModule> apis = doc.getApiModules();
        List<ApiModule> oldapis = olddoc.getApiModules();
        Map<String, ApiModule> oldapimap = new HashMap<>();
        for(ApiModule oldapix:oldapis){
            if(apis.stream().noneMatch(x->(joinModuleKeyHandler(x)).equals(joinModuleKeyHandler(oldapix)))) {
                DocChangeModuleDesc vo = new DocChangeModuleDesc();
                vo.setDesc(oldapix.getTitle());
                vo.setType(QuickDocEnum.CHANGE_TYPE.DELETE.code);
                changes.add(vo);
            }else{
                oldapimap.put(joinModuleKeyHandler(oldapix),oldapix);
            }
        }
        for(ApiModule x:apis){
            ApiModule oldapix = oldapimap.get(joinModuleKeyHandler(x));
            if(oldapix == null){
                DocChangeModuleDesc vo = new DocChangeModuleDesc();
                vo.setDesc(x.getTitle());
                vo.setType(QuickDocEnum.CHANGE_TYPE.ADD.code);
                changes.add(vo);
                continue;
            }
            
            boolean changeflag = false;
            DocChangeModuleDesc vo = new DocChangeModuleDesc();
            vo.setDesc(x.getTitle());
            vo.setType(QuickDocEnum.CHANGE_TYPE.CHANGE.code);
            List<DocChangeActionDesc> children = new ArrayList<>();
            vo.setChildren(children);
            
            List<ApiAction> actions = x.getApiActions();
            List<ApiAction> oldactions = oldapix.getApiActions();
            Map<String, ApiAction> oldactionmap = new HashMap<>();
            for(ApiAction y1: oldactions){
                if(actions.stream().noneMatch(actionx->(joinActionKeyHandler((ApiAction)actionx).equals(joinActionKeyHandler(y1))))) {
                    DocChangeActionDesc actionvo = new DocChangeActionDesc();
                    actionvo.setName(y1.getUris().get(0));
                    actionvo.setDesc(y1.getTitle());
                    actionvo.setType(QuickDocEnum.CHANGE_TYPE.DELETE.code);
                    children.add(actionvo);
                    changeflag = true;
                }else{
                    oldactionmap.put(joinActionKeyHandler(y1), y1);
                }
            }
            for(ApiAction y1: x.getApiActions()){
                ApiAction oldactionx = oldactionmap.get(joinActionKeyHandler(y1));
                if(oldactionx == null){
                    DocChangeActionDesc actionvo = new DocChangeActionDesc();
                    actionvo.setName(y1.getUris().get(0));
                    actionvo.setDesc(y1.getTitle());
                    actionvo.setType(QuickDocEnum.CHANGE_TYPE.ADD.code);
                    children.add(actionvo);
                    changeflag = true;
                    continue;
                }
                
                DocChangeActionDesc actionvo = new DocChangeActionDesc();
                actionvo.setName(y1.getUris().get(0));
                actionvo.setDesc(y1.getTitle());
                actionvo.setType(QuickDocEnum.CHANGE_TYPE.CHANGE.code);
                List<DocChangeFieldDesc> reqChildren = new ArrayList<>();
                List<DocChangeFieldDesc> resChildren = new ArrayList<>();
                actionvo.setReqChildren(reqChildren);
                actionvo.setResChildren(resChildren);
                
                Map<String,List<String>> oldreqfieldmap = new LinkedHashMap<>();
                Map<String,List<String>> oldresfieldmap = new LinkedHashMap<>();
                joinFieldKeyHandler(oldactionx.getReqParam(), oldreqfieldmap,null);
                joinFieldKeyHandler(oldactionx.getResParam(), oldresfieldmap,null);
                
                Map<String,List<String>> reqfieldmap = new LinkedHashMap<>();
                Map<String,List<String>> resfieldmap = new LinkedHashMap<>();
                joinFieldKeyHandler(y1.getReqParam(), reqfieldmap,null);
                joinFieldKeyHandler(y1.getResParam(), resfieldmap,null);
                
                boolean actchangeflag1 = changeFieldHandler(reqfieldmap, oldreqfieldmap, reqChildren);
                boolean actchangeflag2 = changeFieldHandler(resfieldmap, oldresfieldmap, resChildren);
                
                if(actchangeflag1 || actchangeflag2){
                    children.add(actionvo);
                    changeflag = true;
                }
            }
            
            if(changeflag) {
                changes.add(vo);
            }
        }
        
        return false;
    }
    private String joinModuleKeyHandler(ApiModule module) {
        return  module.getClassName();
    }
    private String joinActionKeyHandler(ApiAction action) {
        String key = action.getUris().get(0);
        if(action.getMethods()!=null && action.getMethods().isEmpty()){
            key += action.getMethods().get(0);
        }
        return key;
    }
    private void joinFieldKeyHandler(List<ApiField> fields, Map<String,List<String>> fieldmap,String pkey) {
        if(QuickDocC.DEF_KEY_LIST.equals(pkey) || QuickDocC.DEF_KEY_OBJECT.equals(pkey)) {
            pkey = null;
        }
        for(ApiField finfox:fields){
            String name = finfox.getName();
            if(QuickDocC.DEF_KEY_LIST.equals(finfox.getName()) || QuickDocC.DEF_KEY_OBJECT.equals(finfox.getName())) {
                name = pkey;
            }else if(StringUtils.isNotBlank(pkey)) {
                name = pkey+"."+name;
            }
            String pkey2 = name;
            if(finfox.getChildren()!=null && !finfox.getChildren().isEmpty()){
                joinFieldKeyHandler(finfox.getChildren(), fieldmap, pkey2);
            }else{
                if(fieldmap.get(pkey2)!=null){
                    continue;
                }
                fieldmap.put(pkey2, Arrays.asList(finfox.getName(), finfox.getShowType(), finfox.getTitle(), finfox.getDescription()));
            }
       }
    }
    private boolean changeFieldHandler(Map<String,List<String>> reqfieldmap, Map<String, List<String>> oldreqfieldmap, List<DocChangeFieldDesc> reqChildren){
        boolean actchangeflag = false;
        for(Entry<String, List<String>> oldex:oldreqfieldmap.entrySet()){
            if(!reqfieldmap.keySet().contains(oldex.getKey())){
                actchangeflag = true;
                DocChangeFieldDesc fieldvo = new DocChangeFieldDesc();
                fieldvo.setName(oldex.getKey());
                fieldvo.setDesc(oldex.getValue().get(2));
                fieldvo.setType(QuickDocEnum.CHANGE_TYPE.DELETE.code);
                reqChildren.add(fieldvo);
            }
        }
        for(Entry<String, List<String>> ex:reqfieldmap.entrySet()){
            List<String> oldreqfieldx = oldreqfieldmap.get(ex.getKey());
            DocChangeFieldDesc fieldvo = new DocChangeFieldDesc();
            if(oldreqfieldx==null){
                actchangeflag = true;
                fieldvo.setName(ex.getKey());
                fieldvo.setDesc(ex.getValue().get(2));
                fieldvo.setType(QuickDocEnum.CHANGE_TYPE.ADD.code);
                reqChildren.add(fieldvo);
                continue;
            }
           
            if(!oldreqfieldx.get(1).equals(ex.getValue().get(1))
                   || !StringUtils.defaultIfBlank(oldreqfieldx.get(2), "").equals(StringUtils.defaultIfBlank(ex.getValue().get(2),""))
                   || !StringUtils.defaultIfBlank(oldreqfieldx.get(3), "").equals(StringUtils.defaultIfBlank(ex.getValue().get(3),""))
                   ){
                actchangeflag = true;
                fieldvo.setName(ex.getKey());
                fieldvo.setDesc(ex.getValue().get(2));
                fieldvo.setType(QuickDocEnum.CHANGE_TYPE.CHANGE.code);
                reqChildren.add(fieldvo);
            }
        }
        return actchangeflag;
    }
}
